home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 1.0 beta / flock-1.0RC3.en-US.win32.exe / flock / components / flockFavoritesService.js < prev    next >
Text File  |  2007-10-18  |  58KB  |  1,764 lines

  1. // BEGIN FLOCK GPL
  2. // 
  3. // Copyright Flock Inc. 2005-2007
  4. // http://flock.com
  5. // 
  6. // This file may be used under the terms of of the
  7. // GNU General Public License Version 2 or later (the "GPL"),
  8. // http://www.gnu.org/licenses/gpl.html
  9. // 
  10. // Software distributed under the License is distributed on an "AS IS" basis,
  11. // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. // for the specific language governing rights and limitations under the
  13. // License.
  14. // 
  15. // END FLOCK GPL
  16.  
  17. // XPConnect Helpers
  18. var Cc = Components.classes;
  19. var Ci = Components.interfaces;
  20. var Cr = Components.results;
  21.  
  22.  
  23. // Constants 
  24. const FAVORITES_RDF_FILE           = "flock-data.rdf";
  25. const OLD_FAVORITES_RDF_FILE       = "flock_favorites.rdf";
  26. const OLD_FAVORITES_RDF_FILE_RELIC = "flock_favorites_old.rdf";
  27. const FAVORITES_CID                = Components.ID("{3606f63a-f874-42fa-8174-cc9a0dd0a712}");
  28.  
  29. // Contract IDs
  30. const FAVORITES_CONTRACTID     = "@flock.com/favorites-service;1";
  31. const CONTAINER_CONTRACTID     = "@mozilla.org/rdf/container;1";
  32. const DIRECTORY_SVC_CONTRACTID = "@mozilla.org/file/directory_service;1";
  33. const LOCAL_FILE_CONTRACTID    = "@mozilla.org/file/local;1";
  34.  
  35. // Service handles
  36. const RDFS = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
  37. const RDFCU = Cc["@mozilla.org/rdf/container-utils;1"].getService(Ci.nsIRDFContainerUtils);
  38. const PREFS = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
  39. const IOS = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
  40.  
  41. // Namespaces Helpers
  42. function Namespace(ns) { return function (arg) { return RDFS.GetResource(ns+arg); } }
  43. const W3RDF  = Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#");
  44. const NSRDF  = Namespace("http://home.netscape.com/NC-rdf#");
  45. const WBRDF  = Namespace("http://home.netscape.com/WEB-rdf#");
  46. const FLRDF  = Namespace("http://flock.com/rdf#");
  47. const COLRDF = Namespace("urn:flock:collection:");
  48.  
  49. // RDF Properties
  50. const RDF_URL              = NSRDF("URL");
  51. const RDF_TAGS             = FLRDF("tags");
  52. const RDF_TAG              = FLRDF("tag");
  53. const RDF_WSID             = FLRDF("wsid");
  54. const RDF_NAME             = NSRDF("Name");
  55. const RDF_PERMISSIONS      = FLRDF("permissions");
  56. const RDF_TYPE             = W3RDF("type");
  57. const RDF_FAVORITE         = FLRDF("Favorite");
  58. const RDF_COLLECTION       = FLRDF("Collection");
  59. const RDF_FOLDER           = FLRDF("Folder");
  60. const RDF_LIVEMARK         = NSRDF("Livemark");
  61. const RDF_FEEDURL          = NSRDF("FeedURL");
  62. const RDF_FEEDENABLED      = FLRDF("feedEnabled");
  63. const RDF_SHORTCUTURL      = NSRDF("ShortcutURL");
  64. const RDF_DESCRIPTION      = NSRDF("Description");
  65. const RDF_NSYNC            = FLRDF("nsync");
  66. const RDF_LOCAL            = FLRDF("local");
  67. const RDF_LASTMODIFIEDDATE = WBRDF("LastModifiedDate");
  68. const RDF_LASTVISITDATE    = WBRDF("LastVisitDate");
  69. const RDF_ADDEDDATE        = FLRDF("FavoriteAddDate");
  70. const RDF_FAVORITESROOT    = FLRDF("BookmarksRoot");
  71. const RDF_COLLECTIONSROOT  = FLRDF("CollectionsRoot");
  72. const RDF_FOLDERSROOT      = FLRDF("FoldersRoot");
  73. const RDF_TAGSROOT         = FLRDF("TagsRoot");
  74. const RDF_INSTANCEOF       = W3RDF("instanceOf");
  75. const RDF_SEQ              = W3RDF("Seq");
  76. const RDF_FEED             = FLRDF("feed");
  77. const RDF_TOOLBAR          = FLRDF("toolbar");
  78. const RDF_STORE            = FLRDF("store");
  79. const RDF_VERSION          = FLRDF("version");
  80. const RDF_HASFEEDS         = FLRDF("hasFeeds");
  81. const RDF_ISFEED           = FLRDF("isFeed");
  82. const RDF_COOPTYPE         = FLRDF("CoopType");
  83.  
  84. // RDF Helpers
  85. var rdfLiteral = RDFS.GetLiteral;
  86. var rdfDate    = RDFS.GetDateLiteral;
  87. var rdfInt     = RDFS.GetIntLiteral;
  88. function rdfBoolean(b) { return rdfLiteral(b ? "true" : "false"); }
  89. const RDF_TRUE  = rdfBoolean(true);
  90. const RDF_FALSE = rdfBoolean(false);
  91.  
  92. // Prefs
  93. const PREF_INDEXED_INITIAL_FAVORITES = "flock.favorites.indexedInitialFavorites";
  94. const PREF_FIRSTRUN_COMPLETED        = "flock.firstrun.components.browser_startup.completed";
  95. const PREF_MYWORLD_TOPSITES_COUNT    = "flock.myworld.topsites.count";
  96. const PREF_MYWORLD_TOPSITES_COUNT_DEFAULT = 25;
  97.  
  98. const RECOUNT_INTERVAL = 500; // half-second delay in propagating count changes
  99.  
  100. const SYNC_TIMEOUT = 250; // wait 250 ms for the RDF node to fully populate
  101.  
  102. // Cardinal migration
  103. const CARDINAL_BM_TOOLBAR_DEFAULT_NAME = "Favorites Toolbar";
  104.  
  105. function getObserverService() {
  106.   return Cc['@mozilla.org/observer-service;1']
  107.     .getService(Ci.nsIObserverService);
  108. }
  109.  
  110. // Remove Top Site
  111. function removeSite(aFolderURN, oldFav, aCoopDS) {
  112.   if (!oldFav) return;
  113.   var c_Folder = aCoopDS.get(aFolderURN);
  114.   c_Folder.children.remove(oldFav);
  115. }
  116. // Bubble Sorting Routine
  117. function bubbleSort(aFolderURN, newFav, aCompareFunction, aCoopDS) 
  118. {
  119.   var bInserted = false;
  120.  
  121.   var c_Folder = aCoopDS.get(aFolderURN);
  122.  
  123.   // Get Children of c_Folder
  124.   var childEnum = c_Folder.children.enumerate();
  125.  
  126.   var aTopSiteCount = 0;
  127.  
  128.   var bAlreadySeen = false;
  129.  
  130.   var iMaxCount = PREF_MYWORLD_TOPSITES_COUNT_DEFAULT;
  131.   if (PREFS.getPrefType(PREF_MYWORLD_TOPSITES_COUNT)) {
  132.     iMaxCount = PREFS.getIntPref(PREF_MYWORLD_TOPSITES_COUNT);
  133.   } else {
  134.     PREFS.setIntPref(PREF_MYWORLD_TOPSITES_COUNT, iMaxCount);
  135.   }
  136.  
  137.   while (childEnum.hasMoreElements()) {
  138.     aTopSiteCount++;
  139.   
  140.     var child = childEnum.getNext();   
  141.  
  142.     // If we have NOT inserted, and this child is the same as what we want to insert
  143.     // BAIL out.
  144.     if (!bInserted && checkDupe(aFolderURN, newFav, child)) {
  145.       return;
  146.     }
  147.  
  148.     // If we have NOT inserted yet, check for an appropriate spot
  149.     if (!bInserted && (aCompareFunction(newFav,child) >= 0)) {
  150.       var aIndex = c_Folder.children.indexOf(child);
  151.  
  152.       while(c_Folder.children.has(newFav)) {
  153.         c_Folder.children.remove(newFav);
  154.       }
  155.  
  156.       c_Folder.children.insertAt(newFav,aIndex);
  157.  
  158.       bInserted = true;
  159.       break;
  160.     }
  161.   }
  162.  
  163.   // Never inserted
  164.   // no items were in the collection before OR it is the oldest item.  
  165.   // Insert only if there's room
  166.   if (!bInserted && (aTopSiteCount+1 <= iMaxCount)) {
  167.     c_Folder.children.insertAt(newFav,aTopSiteCount+1);
  168.   }
  169.  
  170.  
  171.   // Go through the list again and make sure we remove the max elements
  172.   var e = c_Folder.children.enumerate();
  173.  
  174.   var iIndex = 0;
  175.   while(e.hasMoreElements()) {
  176.     var maxchild = e.getNext();
  177.     iIndex++;
  178.  
  179.     // We're over our allotted amount.. remove!
  180.     if (iIndex > iMaxCount && maxchild) {
  181.       c_Folder.children.remove(maxchild);
  182.     } 
  183.   }
  184. }
  185.  
  186. function checkDupe(aFolderURN, newFav, child) {
  187.   switch(aFolderURN) {
  188.     case "urn:flock:topsites": {
  189.       if(newFav.URL == child.URL)
  190.         return true;
  191.     } break;
  192.     case "urn:flock:topfeeds": {
  193.       if(newFav.URL == child.URL)
  194.         return true;
  195.     } break;
  196.     case "urn:flock:topmedia": {
  197.       if(newFav.name == child.name
  198.           && newFav.service == child.service) {
  199.         return true;
  200.       }
  201.     }
  202.   }
  203.  
  204.   return false;
  205. }
  206.  
  207.  
  208. // This data structure contains all the information we need to determine and
  209. // maintain the list of "top" faves in each of the supported categories (Sites,
  210. // Feeds, Media).  The associative array keys are URNs of the folder nodes.
  211. //  'criteria' - array of predicate-object pairs used to identify the type
  212. //  'triggers' - array of type-source-predicate-object tuples (arc property
  213. //               operations) that will trigger the observer
  214. //  'observer' - callback for triggers, refreshes folder contents
  215. //  'compare'  - function used to sort items of that type
  216. var gTopFaves = [
  217.   {
  218.     urn: "urn:flock:topsites",
  219.     criteria: [[FLRDF("flockType"), rdfLiteral("bookmark")]],
  220.     triggers: [[Ci.flockIRDFObserver.WATCH_TYPES, null, WBRDF("LastModifiedDate"), null],
  221.                [Ci.flockIRDFObserver.TYPE_ALL, null, WBRDF("LastVisitDate"), null]
  222.               ],
  223.     observer: {
  224.       rdfChanged: function (aDS, aType, aSource, aPredicate, aTarget, aOldTarget) {
  225.         
  226.         if (!aSource) return;
  227.  
  228.         var fav = this.svc.faves_coop.get_from_resource(aSource);
  229.  
  230.         if (aType == Ci.flockIRDFObserver.TYPE_UNASSERT) {
  231.           // Remove this from topsites
  232.           removeSite(this.ft.urn, fav, this.svc.faves_coop);
  233.         } else {
  234.           bubbleSort(this.ft.urn, fav, this.ft.compare, this.svc.faves_coop);
  235.         }
  236.         
  237.         var obs = getObserverService();
  238.         obs.notifyObservers(null, 'refresh-myworld-topsites', false);
  239.       }
  240.     },
  241.     compare: function (aResourceA, aResourceB) {
  242.       // Compare the two resource's timestamp
  243.       // -1 = A older than B
  244.       // 0  = equivalent
  245.       // 1  = A newer than B
  246.       return aResourceA.LastVisitDate - aResourceB.LastVisitDate;
  247.     },
  248.     initialize: function (ft,svc,coopObj)
  249.     {
  250.       if (coopObj == null) {
  251.         coopObj = Cc["@flock.com/singleton;1"]
  252.                   .getService(Ci.flockISingleton)
  253.                   .getSingleton("chrome://flock/content/common/load-faves-coop.js")
  254.                   .wrappedJSObject
  255.                   .bookmarks_root;
  256.       }
  257.       
  258.       var e = coopObj.children.enumerate();
  259.  
  260.       while (e.hasMoreElements()){
  261.         var entry = e.getNext();
  262.  
  263.         if (entry.flockType == "bookmark") {
  264.           bubbleSort(ft.urn, entry, ft.compare, svc.faves_coop);
  265.         } else if (entry.flockType == "folder") {
  266.           this.initialize(ft,svc,entry);
  267.         }
  268.       }
  269.     }
  270.   },
  271.   {
  272.     urn: "urn:flock:topfeeds",
  273.     criteria: [[FLRDF("flockType"), rdfLiteral("feed")]],
  274.     triggers: [[Ci.flockIRDFObserver.TYPE_ALL, null, FLRDF("datevalue"), null],
  275.                [Ci.flockIRDFObserver.TYPE_ALL, null, FLRDF("unseenItems"), null]
  276.               ],
  277.     observer: {
  278.       rdfChanged: function (aDS, aType, aSource, aPredicate, aTarget, aOldTarget) {
  279.         
  280.         if (!aSource) return;
  281.  
  282.         var id = aSource.ValueUTF8;
  283.  
  284.         var sync = {  // Check if it's a feed
  285.             notify: function syncTopFeeds(timer) {
  286.               if (timer) {
  287.                 delete this.svc.syncTimers[id];
  288.               }
  289.  
  290.               if (!checkCriteria(this.ft.criteria,aSource,this.svc.dataSource)) {
  291.                 return; 
  292.               }
  293.  
  294.               // Check parent for feed subscription
  295.               var c_Feed = this.svc.faves_coop.get_from_resource(aSource);
  296.               var parents = c_Feed.getParents();
  297.               for each (var parent in parents) {
  298.                 var node = parent;
  299.                 while (!node.isInstanceOf(this.svc.faves_coop.FeedContext)) {
  300.                   var nodeParents = node.getParents();
  301.                   if (nodeParents.length)
  302.                     node = nodeParents[0];
  303.                   else
  304.                     break;
  305.                 }
  306.                 if (node.id() == "urn:flock:feedcontext:news") {
  307.                   bubbleSort(this.ft.urn, c_Feed, this.ft.compare, this.svc.faves_coop);
  308.                   break;
  309.                 }
  310.               }
  311.             }
  312.           }
  313.  
  314.         sync.ft = this.ft;
  315.         sync.svc = this.svc;
  316.  
  317.         if (this.svc.syncTimers[id]) {
  318.           this.svc.syncTimers[id].cancel();
  319.         }
  320.  
  321.         var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  322.         timer.initWithCallback(sync, SYNC_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
  323.         this.svc.syncTimers[id] = timer;
  324.       }
  325.     },
  326.     compare: function (aResourceA, aResourceB) {
  327.       // A == newFav
  328.       return aResourceA.datevalue - aResourceB.datevalue;
  329.     },
  330.     initialize: function (ft,svc,coopObj)
  331.     {
  332.       if(coopObj == null) {
  333.         coopObj = Cc["@flock.com/singleton;1"]
  334.                   .getService(Ci.flockISingleton)
  335.                   .getSingleton("chrome://flock/content/common/load-faves-coop.js")
  336.                   .wrappedJSObject
  337.                   .get("urn:flock:feedcontext:news");
  338.       }
  339.  
  340.       // If we are still null, forget it
  341.       if(coopObj == null)
  342.         return;
  343.  
  344.       var e = coopObj.children.enumerate();
  345.  
  346.       while (e.hasMoreElements()){
  347.         var entry = e.getNext();
  348.         if(entry.flockType == "feed"){
  349.           bubbleSort(ft.urn, entry, ft.compare, svc.faves_coop);
  350.         } else if (entry.flockType == "folder") {
  351.           this.initialize(ft,svc,entry);
  352.         }
  353.       }
  354.     }
  355.   },
  356.   {
  357.     urn: "urn:flock:topmedia",
  358.     criteria: [[W3RDF("type"), NSRDF("MediaQuery")]],
  359.     triggers: [[Ci.flockIRDFObserver.TYPE_ALL, null, FLRDF("latestDate"), null]],
  360.     observer: {
  361.       rdfChanged: function (aDS, aType, aSource, aPredicate, aTarget, aOldTarget) {
  362.         
  363.         if (!aSource) return;
  364.  
  365.         if(aType == Ci.flockIRDFObserver.TYPE_UNASSERT) {
  366.           // Don't process
  367.           return; 
  368.         }
  369.  
  370.         var id = aSource.ValueUTF8;
  371.  
  372.         var sync = {  // Check if it's a media obj
  373.             notify: function syncTopMedia(timer) {
  374.               if (timer) {
  375.                 delete this.svc.syncTimers[id];
  376.               }
  377.         
  378.               if (!checkCriteria(this.ft.criteria,aSource,this.svc.dataSource)) {
  379.                 return; 
  380.               }
  381.  
  382.               // Check parent for media subscription
  383.               var c_Media = this.svc.faves_coop.get_from_resource(aSource);
  384.               var parents = c_Media.getParents();
  385.               for (var i in parents) {
  386.                 var parent = parents[i];
  387.                 if (parent.id() == "urn:media:favorites") {
  388.                   bubbleSort(this.ft.urn, c_Media, this.ft.compare, this.svc.faves_coop);
  389.                   break;
  390.                 }
  391.               }
  392.             }
  393.           }
  394.  
  395.         sync.ft = this.ft;
  396.         sync.svc = this.svc;
  397.  
  398.         if (this.svc.syncTimers[id]) {
  399.           this.svc.syncTimers[id].cancel();
  400.         }
  401.  
  402.         var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  403.         timer.initWithCallback(sync, SYNC_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
  404.         this.svc.syncTimers[id] = timer;
  405.       }
  406.     },
  407.     compare: function (aResourceA, aResourceB) {
  408.       // A == newFav
  409.       return aResourceA.latestDate - aResourceB.latestDate;
  410.     },
  411.     initialize: function (ft,svc,coopObj)
  412.     {
  413.       if(coopObj == null) {
  414.         coopObj = Cc["@flock.com/singleton;1"]
  415.                   .getService(Ci.flockISingleton)
  416.                   .getSingleton("chrome://flock/content/common/load-faves-coop.js")
  417.                   .wrappedJSObject
  418.                   .get("urn:media:favorites");
  419.       }
  420.  
  421.       // If we are still null, forget it
  422.       if(coopObj == null)
  423.         return;
  424.  
  425.       var e = coopObj.children.enumerate();
  426.  
  427.       while (e.hasMoreElements()){
  428.         var entry = e.getNext();
  429.         // JMC - MediaQuery is no longer CountedObject
  430.         if(entry.flockType == "media"){
  431.           bubbleSort(ft.urn, entry, ft.compare, svc.faves_coop);
  432.         } else if (entry.flockType == "folder") {
  433.           this.initialize(ft,svc,entry);
  434.         }
  435.       }
  436.     }
  437.   }
  438. ];
  439.  
  440. // checks incoming criteria for a match
  441. function checkCriteria(aCriteriaArr,aSource,aDataSource) 
  442. {
  443.   aSource.QueryInterface(Components.interfaces.nsIRDFResource);
  444.  
  445.  
  446.   for (var i=0;i<aCriteriaArr.length;i++) {
  447.     var prop = aCriteriaArr[i][0].QueryInterface(Ci.nsIRDFResource); 
  448.     var value = aCriteriaArr[i][1].QueryInterface(Ci.nsIRDFNode); 
  449.  
  450.     if(!aDataSource.HasAssertion(aSource, prop, value, true)) {
  451.       return false;
  452.     }
  453.   }
  454.  
  455.   return true;
  456. }
  457.  
  458. // Component Utils (just used for the timer/scheduler)
  459. var gCompTK;
  460. function getCompTK() {
  461.   if (!gCompTK) {
  462.     var gCompTK = Cc["@flock.com/singleton;1"]
  463.       .getService(Ci.flockISingleton)
  464.       .getSingleton("chrome://browser/content/flock/services/common/load-compTK.js")
  465.       .wrappedJSObject;
  466.   }
  467.   return gCompTK;
  468. }
  469.  
  470. function loadSubScript(spec)
  471. {
  472.   var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
  473.     .getService(Ci.mozIJSSubScriptLoader);
  474.   var context = {};
  475.   loader.loadSubScript(spec, context);
  476.   return context;
  477. }
  478.  
  479.  
  480. // nsISimpleEnumerator implementation 
  481. function simpleEnumerator(aArray)
  482. {
  483.   aArray.hasMoreElements = function () { 
  484.     return this.length != 0; 
  485.   }
  486.   aArray.getNext = function () { 
  487.     return this.shift(); 
  488.   }
  489.   return aArray;
  490. }
  491.  
  492.  
  493. function makeNSIArray(aArray) {
  494.   aArray.queryElementAt = function (aIndex, aIIDRef) {
  495.     return aArray[aIndex].QueryInterface(aIIDRef);
  496.   }
  497.   aArray.enumerate = function () { return simpleEnumerator(this); }
  498.   aArray._indexOf = aArray.indexOf;
  499.   aArray.indexOf = function (aStartIndex, aElement) { return this._indexOf(aElement, aStartIndex); }
  500.   return aArray;
  501. }
  502.  
  503.  
  504. function getProfDirFile()
  505. {
  506.   var dirService = Cc[DIRECTORY_SVC_CONTRACTID].getService(Ci.nsIProperties);
  507.   
  508.  
  509.   // create a datasource in the profile directory
  510.   var profd = dirService.get("ProfD", Ci.nsIFile);
  511.   var file = Cc[LOCAL_FILE_CONTRACTID].createInstance(Ci.nsILocalFile);
  512.   file.initWithPath(profd.path);
  513.   return file;
  514. }
  515.  
  516.  
  517. function base64Encode(aInput) {
  518.   var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  519.   var output = "";
  520.   while (aInput.length > 0) {
  521.     output += chars[aInput.charCodeAt(0) >> 2];
  522.     output += chars[((aInput.charCodeAt(0) & 0x03) << 4) | 
  523.       (aInput.length > 1 ? ((aInput.charCodeAt(1) & 0xF0) >> 4) : 0)];
  524.     output += chars[aInput.length > 1 ?
  525.       ((aInput.charCodeAt(1) & 0x0F) << 2) | 
  526.       (aInput.length > 2 ? ((aInput.charCodeAt(2) & 0xC0) >> 6) : 0) : 64];
  527.     output += chars[aInput.length > 2 ?
  528.       (aInput.charCodeAt(2) & 0x3F) : 64];
  529.     if (aInput.length > 3) {
  530.       aInput = aInput.substr(3);
  531.     } else {
  532.       break;
  533.     }
  534.   }
  535.   return output;
  536. }
  537.  
  538.  
  539.  
  540. // ======================================================
  541. // ========== BEGIN favoritesService Component ==========
  542. // ======================================================
  543.  
  544.  
  545. // BEGIN Constructor
  546. function favoritesService()
  547. {
  548.   this._logger = Cc["@flock.com/logger;1"].createInstance(Ci.flockILogger);
  549.   this._logger.init("favoritesService");
  550.   this._logger.info("Created Favorites Service Object");
  551.  
  552.   // JMC - Delay init until the DIRService Exists
  553.   this.loaded = false;
  554. }
  555. // END Constructor
  556.  
  557.  
  558. // BEGIN nsISupports
  559. favoritesService.prototype.QueryInterface =
  560. function (aIID)
  561. {
  562.   if ( !aIID.equals(Ci.nsIBookmarksService) && 
  563.        !aIID.equals(Ci.flockIFavoritesService) &&
  564.        !aIID.equals(Ci.flockIFeedContextObserver) &&
  565.        !aIID.equals(Ci.flockIMigratable) &&
  566.        !aIID.equals(Ci.nsICharsetResolver) &&
  567.        !aIID.equals(Ci.nsIClassInfo) &&
  568.        !aIID.equals(Ci.nsIObserver) &&
  569.        !aIID.equals(Ci.nsISupports) )
  570.   {
  571.     throw Cr.NS_ERROR_NO_INTERFACE;
  572.   }
  573.   return this;
  574. }
  575. // END nsISupports
  576.  
  577.  
  578. // BEGIN nsIClassInfo
  579. favoritesService.prototype.flags = Ci.nsIClassInfo.SINGLETON;
  580. favoritesService.prototype.classDescription = "Flock Favorites Service";
  581. favoritesService.prototype.getInterfaces = function (count) {
  582.   var interfaceList = [ Ci.nsIBookmarksService, Ci.flockIFavoritesService,
  583.                         Ci.nsICharsetResolver,
  584.                         Ci.flockIMigratable, Ci.nsIClassInfo ];
  585.   count.value = interfaceList.length;
  586.   return interfaceList;
  587. }
  588. favoritesService.prototype.getHelperForLanguage = function (count) { return null; }
  589. // END nsIClassInfo
  590.  
  591.  
  592. // BEGIN nsIObserver
  593. favoritesService.prototype.observe =
  594. function favoritesService_observe(subject, topic, data)
  595. {
  596.   if ( topic == "nsPref:changed" &&
  597.        data == PREF_FIRSTRUN_COMPLETED &&
  598.        PREFS.getBoolPref(PREF_FIRSTRUN_COMPLETED) )
  599.   {
  600.     for (var folderURN in gTopFaves) {
  601.       var faveType = gTopFaves[folderURN];
  602.  
  603.       // start gTopFaves init function
  604.       faveType.initialize(faveType,this,null);
  605.     }
  606.  
  607.     if (!PREFS.getBoolPref(PREF_INDEXED_INITIAL_FAVORITES)) {
  608.       var index_initial_callback = {};
  609.       index_initial_callback.svc = this;
  610.       index_initial_callback.notify = function (timer) {
  611.         var e = this.svc.getAllFavorites();
  612.         while (e.hasMoreElements()) {
  613.           var u = e.getNext().QueryInterface(Ci.nsIRDFResource).ValueUTF8;
  614.           //this.svc.luceneIndexer.AddDocument(u);
  615.         }
  616.         PREFS.setBoolPref(PREF_INDEXED_INITIAL_FAVORITES, true);
  617.       }
  618.     }
  619.   }
  620.  
  621.   if (topic == "flock-shutdown") {
  622.     this.obs.removeObserver(this, "flock-shutdown");
  623.     this._shutdown();
  624.   }
  625. }
  626. // END nsIObserver
  627.  
  628.  
  629. // BEGIN flockIFeedContextObserver
  630. favoritesService.prototype.onSubscribe = function(context, feed)
  631. {
  632.   function compare(a, b) {
  633.     return a.datevalue - b.datevalue;
  634.   }
  635.   var id = this.faves_coop.Feed.get_id({ URL: feed.getURL().spec });
  636.   var obj = this.faves_coop.get(id);
  637.   bubbleSort("urn:flock:topfeeds", obj, compare, this.faves_coop);
  638. }
  639.  
  640. favoritesService.prototype.onUnsubscribe = function(context, feed)
  641. {
  642.   var id = this.faves_coop.Feed.get_id({ URL: feed.getURL().spec });
  643.   var obj = this.faves_coop.get(id);
  644.   removeSite("urn:flock:topfeeds", obj, this.faves_coop);
  645. }
  646.  
  647. favoritesService.prototype.onUnreadCountChange = function(context, unreadCount)
  648. {
  649. }
  650. // END flockIFeedContextObserver
  651.  
  652.  
  653. // BEGIN flockIFavoritesService
  654. favoritesService.prototype.setupDefaultFavorites = function ()
  655.   this._logger.info("SetupDefaultFavorites");
  656.   var faves_coop = this.faves_coop;
  657.   var favSrv = this;
  658.   var context = {coop: faves_coop, service: favSrv, dump: dump};
  659.   var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
  660.     .getService(Ci.mozIJSSubScriptLoader);
  661.   loader.loadSubScript("chrome://flock/locale/favorites/defaultFavorites.js", context);
  662.   
  663.   // Load up the defaul media favs too.
  664.   loader.loadSubScript("chrome://flock/locale/photo/defaultMedia.js", context);
  665. }
  666.  
  667. favoritesService.prototype.init = 
  668. function favoritesService_init(aDataSource)
  669. {
  670.   if (this.loaded) return;
  671.  
  672.   aDataSource.QueryInterface(Ci.flockIRDFObservable);
  673.  
  674.   this.dataSource = aDataSource;
  675.   this.faves_coop = Cc["@flock.com/singleton;1"]
  676.                     .getService(Ci.flockISingleton)
  677.                     .getSingleton("chrome://flock/content/common/load-faves-coop.js")
  678.                     .wrappedJSObject;
  679.  
  680.   this.syncTimers = {};
  681.  
  682.   var prefsService = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService);
  683.   var favesBranch = prefsService.getBranch("flock.favorites.");
  684.  
  685.   // Convert 0.9.0.x LastVisitDate/LastModifiedDate from strings to real dates
  686.   var migratedStringDates;
  687.   try {
  688.     migratedStringDates = favesBranch.getBoolPref("migratedStringDates");
  689.   } catch (ex) {
  690.     migratedStringDates = false;
  691.   }
  692.  
  693.   if (!migratedStringDates) {
  694.     var inst = this;
  695.  
  696.     function convertOldDate(aResource, aProp) {
  697.       var target = inst.dataSource.GetTarget(aResource, aProp, true);
  698.       if (target && target instanceof Ci.nsIRDFLiteral) {
  699.         var dateStr = target.QueryInterface(Ci.nsIRDFLiteral).Value;
  700.         var time = Date.parse(dateStr);
  701.         if (!isNaN(time)) {
  702.           inst.dataSource.Change(aResource, aProp, target,
  703.                                  RDFS.GetDateLiteral(1000 * time));
  704.         } else {
  705.           inst.dataSource.Unassert(aResource, aProp, target);
  706.         }
  707.       }
  708.     }
  709.  
  710.     var resources = this.faves_coop.Bookmark.all_ids();
  711.     while (resources.hasMoreElements()) {
  712.       var resource = resources.getNext();
  713.       convertOldDate(resource, RDF_LASTVISITDATE);
  714.       convertOldDate(resource, RDF_LASTMODIFIEDDATE);
  715.     }
  716.  
  717.     favesBranch.setBoolPref("migratedStringDates", true);
  718.   }
  719.  
  720.   // Make sure no favorite has more than one parent (only once)
  721.   var uniqueId = false;
  722.   try {
  723.     uniqueId = favesBranch.getBoolPref("uniqueid");
  724.   } catch(e) { /* The pref is not set yet */ }
  725.   if (!uniqueId) {
  726.     for (var i in gTopFaves) {
  727.       // Change the top favorites from the Folder type to TopFavesFolder
  728.       try {
  729.         var res = RDFS.GetResource(gTopFaves[i].urn);
  730.         var oldtype = this.dataSource.GetTarget(res, W3RDF("type"), true);
  731.         var oldcooptype = this.dataSource.GetTarget(res, FLRDF("CoopType"), true);
  732.         if (oldtype)
  733.           this.dataSource.Change(res, W3RDF("type"), oldtype, FLRDF("TopFavesFolder"));
  734.         if (oldcooptype)
  735.           this.dataSource.Change(res, FLRDF("CoopType"), oldcooptype, FLRDF("TopFavesFolder"));
  736.       }
  737.       catch (e) {}
  738.     }
  739.   
  740.     var allFaves = this.faves_coop.Bookmark.all();
  741.     var nonUnique = [];
  742.     while (allFaves.hasMoreElements()) {
  743.       var fave = allFaves.getNext();
  744.       // Assuming all bookmarks with an id starting with "urn:" are online
  745.       // faves, and thus should not be transformed.
  746.       var urn = fave.id();
  747.       if (urn.substr(0, 4) == "urn:") {
  748.         continue;
  749.       }
  750.       var parentCount = 0;
  751.       for each (var parent in fave.getParents()) {
  752.         if (parent instanceof this.faves_coop.Folder)
  753.           parentCount++;
  754.       }
  755.       if (parentCount > 1) {
  756.         nonUnique.push(fave);
  757.       }
  758.     }
  759.     
  760.     for each (var fave in nonUnique) {
  761.       // This favorite has more than one parent.
  762.       // We need to clone it so each favorite has only one parent.
  763.       var firstparent = true;
  764.       var faveRes = RDFS.GetResource(fave.id());
  765.       for each (var parent in fave.getParents()) {
  766.         if (parent instanceof this.faves_coop.Folder) {
  767.           if (firstparent) {
  768.             // The first parent keeps the original favorite
  769.             firstparent = false;
  770.           }
  771.           else {
  772.             // Other parents get a clone
  773.             var newRes = this.cloneResource(faveRes);
  774.             var newFave = this.faves_coop.get(newRes.Value);
  775.  
  776.             var position = parent.children.indexOf(fave);
  777.             parent.children.insertAt(newFave, position);
  778.             parent.children.remove(fave);
  779.           }
  780.         }
  781.         else {
  782.           // MyWorld's "Top Faves" keeps the original
  783.         }
  784.       }
  785.     }
  786.     favesBranch.setBoolPref ("uniqueid", true);
  787.   }
  788.   
  789.   this._obsObsrv = {};
  790.   this._obsObsrv.observe = function main_observer(aSubject, aTopic, aVerb) {
  791.     
  792.     if( aTopic == "myworld-enableDisable-observers" ) {
  793.       if( aVerb == "true" ) {
  794.         // Re-enable the arc observers
  795.         for (var folderURN in this.gtf) {
  796.           // Make sure the sorting folders exist for each type
  797.           var faveType = this.gtf[folderURN];
  798.           faveType.observer.ft = faveType;
  799.           faveType.observer.svc = this.svc;
  800.           faveType.root = this.fc.get(faveType.urn);
  801.           if (!faveType.root) { faveType.root = new this.fc.TopFavesFolder(faveType.urn); }
  802.           // Start watching for the appropriate arc changes
  803.           for (var t in faveType.triggers) {
  804.             var trig = faveType.triggers[t];
  805.             this.ds.addArcObserver(trig[0], trig[1], trig[2], trig[3], faveType.observer);
  806.           }
  807.         }
  808.  
  809.         var feedManager = Cc["@flock.com/feed-manager;1"].getService(Ci.flockIFeedManager);
  810.         var newsFeedContext = feedManager.getFeedContext("news");
  811.         newsFeedContext.addObserver(this.svc);
  812.       } else {
  813.         // remove the arc observers
  814.         for (var folderURN in this.gtf) {
  815.           // Make sure the sorting folders exist for each type
  816.           var faveType = this.gtf[folderURN];
  817.           faveType.observer.ft = faveType;
  818.           faveType.observer.svc = this.svc;
  819.           faveType.root = this.fc.get(faveType.urn);
  820.           if (!faveType.root) { faveType.root = new this.fc.TopFavesFolder(faveType.urn); }
  821.           // Start watching for the appropriate arc changes
  822.           for (var t in faveType.triggers) {
  823.             var trig = faveType.triggers[t];
  824.             this.ds.removeArcObserver(trig[0], trig[1], trig[2], trig[3], faveType.observer);
  825.           }
  826.         }
  827.  
  828.         var feedManager = Cc["@flock.com/feed-manager;1"].getService(Ci.flockIFeedManager);
  829.         var newsFeedContext = feedManager.getFeedContext("news");
  830.         newsFeedContext.removeObserver(this.svc);
  831.       }
  832.     }
  833.   }
  834.   this._obsObsrv.fc   = this.faves_coop;
  835.   this._obsObsrv.svc  = this;
  836.   this._obsObsrv.ds   = this.dataSource;
  837.   this._obsObsrv.gtf  = gTopFaves;
  838.  
  839.   this._obsObsrv.observe(null, "myworld-enableDisable-observers", "true");
  840.  
  841.   this.obs = Cc["@mozilla.org/observer-service;1"]
  842.     .getService(Ci.nsIObserverService);
  843.     
  844.   this.obs.addObserver(this._obsObsrv, "myworld-enableDisable-observers", false);
  845.   this.obs.addObserver(this, "flock-shutdown", false);
  846.  
  847.   // set up a prefs observer for the webservice id
  848.   var prefBranch2 = PREFS.QueryInterface(Ci.nsIPrefBranch2);
  849.   prefBranch2.addObserver(PREF_FIRSTRUN_COMPLETED, this, false);
  850.  
  851.   this.deleteTransients();
  852.   this.loaded = true;
  853.   this._logger.info("flock favorites data source set up");    
  854. }
  855.  
  856. favoritesService.prototype._shutdown =
  857. function favoritesService__shutdown()
  858. {
  859.   for (let [id, timer] in Iterator(this.syncTimers)) {
  860.     var sync = timer.callback;
  861.     timer.cancel();
  862.     sync.notify(null);
  863.   }
  864.  
  865.   this.syncTimers = {};
  866.  
  867.   var prefBranch2 = PREFS.QueryInterface(Ci.nsIPrefBranch2);
  868.   prefBranch2.removeObserver(PREF_FIRSTRUN_COMPLETED, this);
  869.  
  870.   try {
  871.     this.deleteTransients();
  872.   }
  873.   catch (ex) {
  874.     this._logger.debug("ERROR deleting transient data!");
  875.     this._logger.debug(ex);
  876.   }
  877.  
  878.   this.obs.removeObserver(this._obsObsrv, "myworld-enableDisable-observers");
  879.  
  880.   this.loaded = false;
  881. }
  882.  
  883. favoritesService.prototype.createBookmarkWithTags =
  884. function (aName, aURL, aShortcutURL, aDescription, aDocCharSet, aPostData, aTags)
  885. {
  886.   var bookmark = this.createNewBookmarkWithTags(aName, aURL, aShortcutURL, aDescription,
  887.                                                 aDocCharSet, aPostData, aTags);
  888.   this.faves_coop.bookmarks_root.children.addOnce(bookmark);
  889.   return RDFS.GetResource(bookmark.id())
  890. }
  891.  
  892. favoritesService.prototype.addBookmarkToFolder =
  893. function (aFolderName, aName, aURL, aShortcutURL, aDescription, aDocCharSet, aPostData, aTags)
  894. {
  895.   var folder = this.faves_coop.get(aFolderName);
  896.   var bookmark = this.createNewBookmarkWithTags(aName, aURL, aShortcutURL, aDescription,
  897.                                                 aDocCharSet, aPostData, aTags);
  898.   folder.children.addOnce(bookmark);
  899.   return RDFS.GetResource(bookmark.id());
  900. }
  901.  
  902. favoritesService.prototype.writeBookmarks =
  903. function (aBookmarksFile)
  904. {
  905.   var bsvc = Cc["@mozilla.org/browser/bookmarks-service;1"].getService(Ci.flockIBookmarksService);
  906.   var dirService = Cc[DIRECTORY_SVC_CONTRACTID].getService(Ci.nsIProperties);
  907.   var ds = RDFS.GetDataSource("rdf:flock-favorites").QueryInterface(Ci.nsIRDFDataSource);
  908.   var resource = RDF_FOLDERSROOT;
  909.   bsvc.WriteBookmarks(aBookmarksFile, ds, resource);
  910. }
  911.  
  912. favoritesService.prototype.importBookmarks =
  913. function favoritesService_importBookmarks(aBookmarksFile)
  914. {
  915.   // Make sure the file exists and is readable
  916.   aBookmarksFile.QueryInterface(Ci.nsIFile);
  917.   if ((!aBookmarksFile.exists()) || (!aBookmarksFile.isReadable())) {
  918.     throw Cr.NS_ERROR_UNEXPECTED;
  919.   }
  920.  
  921.   // Load the contents of the file into 'lines' array
  922.   var fis = Cc["@mozilla.org/network/file-input-stream;1"]
  923.     .createInstance(Ci.nsIFileInputStream);
  924.   fis.init(aBookmarksFile, 1, 0, 0);
  925.   fis.QueryInterface(Ci.nsILineInputStream);
  926.   var lines = []; var line = {};
  927.   while (fis.readLine(line)) { lines.push(line.value); }
  928.  
  929.   // Local functions to import bookmarks and folders
  930.   var inst = this;
  931.   var decodeEntities = function (str) {
  932.     return str.replace(/</g, "<")
  933.               .replace(/>/g, ">")
  934.               .replace(/&/g, "&")
  935.               .replace(/"/g, "\"")
  936.               .replace(/'/g, "'");
  937.   };
  938.   var importBookmark = function () {
  939.     var url, lastVisit, lastMod, id, name, desc, feedurl;
  940.     if (lines[i].match(/ HREF="([^"]*)"/)) { url = RegExp.$1; }
  941.     if (lines[i].match(/ FEEDURL="([^"]*)"/)) { feedurl = RegExp.$1; }
  942.     if (lines[i].match(/ LAST_VISIT="([^"]*)"/)) { lastVisit = RegExp.$1; }
  943.     if (lines[i].match(/ LAST_MODIFIED="([^"]*)"/)) { lastMod = RegExp.$1; }
  944.     if (lines[i].match(/ ID="([^"]*)"/)) { id = RegExp.$1; }
  945.     if (lines[i].match(/">(.*)<\/A>/)) { name = decodeEntities(RegExp.$1); }
  946.     if (lines[i+1].match(/^ *<DD>(.*)$/)) { desc = decodeEntities(RegExp.$1); i++; }
  947.     var bmRes;
  948.     if (feedurl) {
  949.       bmRes = inst.createLivemark(name, url, feedurl, desc);
  950.     }
  951.     else if (url == undefined) {
  952.       bmRes = inst.createSeparator();
  953.     }
  954.     else {
  955.       bmRes = inst.createBookmarkInternal(name, url, desc, new Date(lastVisit), new Date(lastMod));
  956.     }
  957.     return inst.faves_coop.get(bmRes.Value);
  958.   };
  959.   var importFolder = function () {
  960.     lines[i++].match(/">(.*)<\/H3/i);
  961.     var folderRes = inst.createFolder(RegExp.$1);
  962.     var c_folder = inst.faves_coop.get(folderRes.Value);
  963.     inst.faves_coop.bookmarks_root.children.remove(c_folder);
  964.     if (lines[i].match(/^ *<DD>(.*)$/i)) {
  965.       c_folder.description = decodeEntities(RegExp.$1);
  966.       i++;
  967.     }
  968.     if (lines[i].match(/^ *<DL><p>/i)) {
  969.       // This folder contains stuff
  970.       importContents(c_folder);
  971.     }
  972.     return c_folder;
  973.   };
  974.   var importContents = function (aParentFolder) {
  975.     i++; // Increment past <DL><p>
  976.     while (i < lines.length) {
  977. //      inst._logger.warn("\n Parent: "+aParentFolder.Value+" line: "+lines[i]);
  978.       var child = null;
  979.       if (lines[i].match(/^ *<DT><A /i)) {
  980.         child = importBookmark();
  981.       } else if (lines[i].match(/^ *<DT><H3/i)) {
  982.         child = importFolder();
  983.       } else if (lines[i].match(/^ *<\/DL><p>/i)) {
  984.         return;
  985.       }
  986.       if (child) {
  987.         aParentFolder.children.addOnce(child);
  988.       }
  989.       i++;
  990.     }
  991.   };
  992.  
  993.   // Begin parsing the file
  994.   var i = 0;
  995.   while ((!lines[i].match(/^ *<DL><p>/i)) && (i < lines.length)) { i++; }
  996.   var folder = this.faves_coop.get(this.createFolder("Imported Bookmarks").Value);
  997.   importContents(folder);
  998. }
  999. // END flockIFavoritesService
  1000.  
  1001.  
  1002.  
  1003. // BEGIN nsIBookmarksService
  1004. favoritesService.prototype.addBookmarkImmediately = function (aURI, aTitle, bmType, aDocCharSet) 
  1005.   return this.createBookmark(aName, aURI, aURI, "", aDocCharSet, null);
  1006. }
  1007.  
  1008. favoritesService.prototype.cloneResource = function (aSource) 
  1009. {
  1010.   var newResource = RDFS.GetAnonymousResource();
  1011.  
  1012.   // We need to create CoopType first or the indexer complains
  1013.   var coopType = this.dataSource.GetTarget(aSource, RDF_COOPTYPE, true);
  1014.   if (coopType)
  1015.     this.dataSource.Assert(newResource, RDF_COOPTYPE, coopType, true);
  1016.  
  1017.   var arcs = this.dataSource.ArcLabelsOut(aSource);
  1018.  
  1019.   while (arcs.hasMoreElements()) {
  1020.     var property = arcs.getNext();
  1021.     property = property.QueryInterface(Ci.nsIRDFResource);
  1022.     if (property.EqualsNode(this.getBookmarksToolbarFolder())) continue;
  1023.     if (property.EqualsNode(RDF_COOPTYPE)) continue; // Already created
  1024.     if (property.EqualsNode(FLRDF('isIndexable'))) continue; // Will be created later
  1025.     
  1026.     var target = this.dataSource.GetTarget(aSource, property, true);
  1027.     
  1028.     // Test if the arc points to a child.
  1029.     if (RDFCU.IsOrdinalProperty(property)) {
  1030.       var oldChild = target.QueryInterface(Ci.nsIRDFResource);
  1031.       var newChild = this.cloneResource(oldChild);
  1032.       this.dataSource.Assert(newResource, property, newChild, true);
  1033.     } else {
  1034.       this.dataSource.Assert(newResource, property, target, true);
  1035.     }
  1036.   }
  1037.   
  1038.   // Create isIndexable last to avoid firing the indexer too often
  1039.   var isIndexable = this.dataSource.GetTarget(aSource, FLRDF('isIndexable'), true);
  1040.   if (isIndexable)
  1041.     this.dataSource.Assert(newResource, FLRDF('isIndexable'), isIndexable, true);
  1042.   
  1043.   return newResource;
  1044. }
  1045.  
  1046. favoritesService.prototype.createBookmark =
  1047. function (aName, aURL, aShortcutURL, aDescription, aDocCharSet, aPostData)
  1048. {
  1049.   if (!aURL || aURL == null) aURL = "about:blank";
  1050.  
  1051.   var date = new Date();
  1052.   var bookmark = new this.faves_coop.Bookmark(
  1053.    {
  1054.       name: aName,
  1055.       URL: aURL,
  1056.       description: aDescription,
  1057.       BookmarkAddDate: date,
  1058.       LastVisitDate: date
  1059.     }
  1060.   );
  1061.   return RDFS.GetResource(bookmark.id());
  1062. }
  1063.  
  1064. favoritesService.prototype.createBookmarkInternal =
  1065. function (aName, aURL, aDescription, aLastVisit, aLastMod)
  1066. {
  1067.   if (!aURL || aURL == null) aURL = "about:blank";
  1068.  
  1069.   var bookmark = new this.faves_coop.Bookmark(
  1070.      {
  1071.       name: aName,
  1072.       URL: aURL,
  1073.       description: aDescription,
  1074.       LastVisitDate: aLastVisit,
  1075.       LastModifiedDate: aLastMod
  1076.     }
  1077.   );
  1078.   return RDFS.GetResource(bookmark.id());
  1079. }
  1080.  
  1081. favoritesService.prototype.createFolder = function (aName) 
  1082. {
  1083.   if (!aName || aName == null) aName = "New Folder";
  1084.   var folder = new this.faves_coop.Folder({
  1085.     BookmarkAddDate: new Date(),
  1086.     name: aName
  1087.   });
  1088.   this.faves_coop.bookmarks_root.children.addOnce(folder);
  1089.   return RDFS.GetResource(folder.id());
  1090. }
  1091.  
  1092. favoritesService.prototype.createFolderInContainer =
  1093. function (aName, aParentFolder, aIndex)
  1094. {
  1095.   var folder = new this.faves_coop.Folder({
  1096.     BookmarkAddDate: new Date(),
  1097.     name: aName
  1098.   });
  1099.  
  1100.   // default to the favorites_root folder...
  1101.   var parent = this.faves_coop.favorites_root;
  1102.   if (aParentFolder) {
  1103.     // FIXME: get should be able to take an nsIRDFResource as an id
  1104.     parent = this.faves_coop.get(aParentFolder.ValueUTF8)
  1105.   }
  1106.   if (aIndex < 0)
  1107.     parent.children.add(folder);
  1108.   else
  1109.     parent.children.insertAt(folder, aIndex);
  1110.  
  1111.   return RDFS.GetResource(folder.id());
  1112. }
  1113.  
  1114. favoritesService.prototype.createLivemark =
  1115. function (aName, aURL, aRSSURL, aDescription)
  1116. {
  1117.   var feedService = Cc["@flock.com/feed-manager;1"].getService(Ci.flockIFeedManager);
  1118.   var livemarkContext = feedService.getFeedContext("livemarks");
  1119.   var feed = livemarkContext.getRoot().subscribeURL(aRSSURL, aName);
  1120.   return RDFS.GetResource(feed.id());
  1121. }
  1122.  
  1123. favoritesService.prototype.createLivemarkInContainer =
  1124. function FS_createLivemarkInContainer(aName, aURL, aRSSURL, aDescription, aFolder, aIndex)
  1125. {
  1126.   var feedService = Cc["@flock.com/feed-manager;1"].getService(Ci.flockIFeedManager);
  1127.   var livemarkContext = feedService.getFeedContext("livemarks");
  1128.  
  1129.   parent_ = this.faves_coop.get(aFolder.ValueUTF8)
  1130.   if (!parent_) { // Invalid folder
  1131.     throw Components.Exception('createLivemarkInContainer: invalid folder '+aFolder.ValueUTF8,
  1132.                                 Cr.NS_ERROR_UNEXPECTED);
  1133.   }
  1134.  
  1135.   var feed = livemarkContext.getRoot().subscribeURL(aRSSURL, aName);
  1136.   var coopFeed = this.faves_coop.get(feed.id());
  1137.  
  1138.   if (aIndex < 0) {
  1139.     parent_.children.add(coopFeed);
  1140.   }
  1141.   else {
  1142.     parent_.children.insertAt(coopFeed, aIndex);
  1143.   }
  1144. }
  1145.  
  1146. favoritesService.prototype.createSeparator = function ()
  1147.   var newSeparator = new this.faves_coop.Separator();
  1148.   return RDFS.GetResource(newSeparator.id());
  1149. }
  1150.  
  1151. favoritesService.prototype.getBookmarksToolbarFolder = function ()
  1152. {
  1153.   if (this.faves_coop.toolbar.folder) {
  1154.     return RDFS.GetResource(this.faves_coop.toolbar.folder.id());
  1155.   } else {
  1156.     // JMC - Always have a toolbar folder
  1157.     var favesToolbar = new this.faves_coop.Folder({name: "Favorites Toolbar"});
  1158.     this.faves_coop.toolbar.folder = favesToolbar;
  1159.     this.faves_coop.bookmarks_root.children.addOnce(this.faves_coop.toolbar.folder);
  1160.     return this.getBookmarksToolbarFolder();
  1161.   }
  1162. }
  1163.  
  1164. favoritesService.prototype.getLastCharset = function (aURL) { return null; }
  1165.  
  1166. favoritesService.prototype.getParent = function (aSource) 
  1167.   if (!aSource || !aSource.QueryInterface || !aSource.QueryInterface(Ci.nsIRDFNode)) return false;
  1168.  
  1169.   var arcs = this.dataSource.ArcLabelsIn(aSource);
  1170.   while (arcs && arcs.hasMoreElements()) {
  1171.     var resource = arcs.getNext();
  1172.     if (resource && resource.QueryInterface) {
  1173.       resource = resource.QueryInterface(Ci.nsIRDFResource);
  1174.     }
  1175.     if (resource && resource.QueryInterface && RDFCU.IsOrdinalProperty(resource)) {
  1176.       var parent = this.dataSource.GetSource(resource, aSource, true);
  1177.       return parent;
  1178.     }
  1179.   }
  1180.   return null;
  1181. }
  1182.  
  1183. favoritesService.prototype.getParentChain = function (aSource) 
  1184.   var chain = [];
  1185.   var current = this.getParent(aSource);
  1186.   while (current) {
  1187.     this._logger.info("parentChain: "+current);
  1188.     chain.push(current);
  1189.     current = this.getParent(current);
  1190.   }
  1191.   chain.reverse();
  1192.   return makeNSIArray(chain); 
  1193. }
  1194.  
  1195. favoritesService.prototype.isBookmarked = function (aURL)
  1196. {
  1197.   return this.faves_coop.Bookmark.get_from_url(aURL) != null;
  1198. }
  1199.  
  1200. favoritesService.prototype.isBookmarkedResource = function (aResource)
  1201. {
  1202.   return this.faves_coop.Bookmark.exists(aResource.Value);
  1203. }
  1204.  
  1205. favoritesService.prototype.readBookmarks = function () { return; }
  1206.  
  1207. favoritesService.prototype.removeBookmarkIcon = function (aURL) { return; }
  1208.  
  1209. favoritesService.prototype.resolveKeyword = function (aName, aPostData) 
  1210. {
  1211.   var keywordArray = this.faves_coop.Bookmark.find({ShortcutURL: aName})
  1212.   var keyword = null;
  1213.   if (keywordArray && keywordArray.length == 1) {
  1214.     keyword = keywordArray[0].URL;
  1215.   }
  1216.   return keyword; 
  1217. }
  1218.  
  1219. favoritesService.prototype.updateBookmarkIcon =
  1220. function (aURL, aIconMIMEtype, aIconData, aIconDataLen) 
  1221. {
  1222.   var data = "data:";
  1223.   if (aIconData) {
  1224.     iconData = String.fromCharCode.apply(null, aIconData);
  1225.     data += aIconMIMEtype;
  1226.     data += ";base64,";
  1227.     data += base64Encode(iconData);
  1228.   }
  1229.  
  1230.   var faves = this.faves_coop.Bookmark.find({URL: aURL});
  1231.   for (var i = 0; i < faves.length; i++) {
  1232.     var fav = faves[i];
  1233.     fav.favicon = data;
  1234.   }
  1235.   return;
  1236. }
  1237.  
  1238. favoritesService.prototype.updateLastVisitedDate = function (aURL, docCharset) {
  1239.   if (aURL == '') return;
  1240.  
  1241.   var literal = RDFS.GetLiteral(aURL);
  1242.   var sources = this.dataSource.GetSources(RDF_URL, literal, true);
  1243.   var bookmark = null;
  1244.   while (sources && sources.hasMoreElements()) {
  1245.     var source = sources.getNext().QueryInterface(Ci.nsIRDFResource);
  1246.  
  1247.     var ctRes = this.dataSource.GetTarget(source, FLRDF("CoopType"), true);
  1248.     if (ctRes) {
  1249.       ctRes.QueryInterface(Ci.nsIRDFResource);
  1250.       if (ctRes.Value == "http://flock.com/rdf#Bookmark") {
  1251.         bookmark = this.faves_coop.get_from_resource(source);
  1252.         break;
  1253.       }
  1254.     }
  1255.   }
  1256.  
  1257.   if (bookmark) {
  1258.     bookmark.LastVisitDate = new Date();
  1259.   }
  1260.   return;
  1261. }
  1262.  
  1263.  
  1264. //check if string is an integer
  1265. favoritesService.prototype.isInt = function (str) {
  1266.     var i = parseInt (str);
  1267.  
  1268.     if (isNaN (i))
  1269.         return false;
  1270.  
  1271.     i = i . toString ();
  1272.     if (i != str)
  1273.         return false;
  1274.  
  1275.     return true;
  1276. }
  1277. // END nsIBookmarksService
  1278.  
  1279.  
  1280.  
  1281. // BEGIN flockIMigratable
  1282. favoritesService.prototype.needsMigration =
  1283. function favoritesService_needsMigration(oldVersion)
  1284. {
  1285.   var oldFavoritesFile = Cc["@mozilla.org/file/directory_service;1"] 
  1286.     .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
  1287.   oldFavoritesFile.append(OLD_FAVORITES_RDF_FILE);
  1288.  
  1289.   var relicFavoritesFile = Cc["@mozilla.org/file/directory_service;1"] 
  1290.     .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
  1291.   relicFavoritesFile.append(OLD_FAVORITES_RDF_FILE_RELIC);
  1292.  
  1293.   // only migrate if the old favorites rdf file exists
  1294.   // FIXME: after the flock_favorites.rdf file is renamed to flock_favorites_old.rdf
  1295.   // flock_favorites.rdf is created again in the same dir. so we need the 2nd
  1296.   // condition in the IF to stop migration from happening again
  1297.   if (oldFavoritesFile.exists() && !relicFavoritesFile.exists()) {
  1298.     return true;
  1299.   }
  1300.   return false;
  1301. }
  1302.  
  1303. favoritesService.prototype.startMigration = 
  1304. function (oldVersion, aFlockMigrationProgressListener)
  1305. {
  1306.   // Tell myworld to stop observing.
  1307.   var obs = getObserverService();
  1308.   obs.notifyObservers(null, 'myworld-enableDisable-observers', false);
  1309.   
  1310.   var oldFavoritesFile = Cc["@mozilla.org/file/directory_service;1"] 
  1311.     .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
  1312.   oldFavoritesFile.append(OLD_FAVORITES_RDF_FILE);
  1313.  
  1314.   var ctxt = {
  1315.     listener: aFlockMigrationProgressListener,
  1316.     oldFavoritesFile: oldFavoritesFile
  1317.   };
  1318.  
  1319.   if (oldFavoritesFile.exists()) {
  1320.     ctxt.listener.onUpdate(0, 'Migrating favorites');
  1321.   }
  1322.   return { wrappedJSObject: ctxt };
  1323. }
  1324.  
  1325. favoritesService.prototype.doMigrationWork =
  1326. function (ctxtWrapper)
  1327. {
  1328.   var ctxt = ctxtWrapper.wrappedJSObject;
  1329.  
  1330.   if (!ctxt.oldFavoritesFile.exists())
  1331.     return false;
  1332.  
  1333.   if (!ctxt.favoritesMigrator)
  1334.     ctxt.favoritesMigrator = this._migrateFavorites(ctxt);
  1335.   if (ctxt.favoritesMigrator.next())
  1336.     ctxt.favoritesMigrator = null;
  1337.  
  1338.   return Boolean(ctxt.favoritesMigrator);
  1339. }
  1340.  
  1341. favoritesService.prototype.finishMigration = function (ctxtWrapper) { 
  1342.   // Re-init the topfaves
  1343.   for (var folderURN in gTopFaves) {
  1344.     var faveType = gTopFaves[folderURN];
  1345.  
  1346.     // start gTopFaves init function
  1347.     faveType.initialize(faveType,this,null);
  1348.   }  
  1349.   
  1350.   // Tell myworld to resume rdf observation
  1351.   var obs = getObserverService();
  1352.   obs.notifyObservers(null, 'myworld-enableDisable-observers', true);
  1353. }
  1354. // END flockIMigratable
  1355.  
  1356.  
  1357. favoritesService.prototype.deleteTransients =
  1358. function favoritesService_deleteTransients()
  1359. {
  1360.   this._logger.info(".deleteTransients()");
  1361.   var types = ["OnlineBookmarksStream", "MediaQuery"];
  1362.   for (var t in types) {
  1363.     var type = types[t];
  1364.     this._logger.info(" deleting transients of type '"+type+"'");
  1365.     var transientArray = this.faves_coop[type].find({isTransient: true});
  1366.     for (var i = 0; i < transientArray.length; i++) {
  1367.       var transient_ = transientArray[i];
  1368.       this._logger.info('gonna remove transient named ' + transient_.name);
  1369.       var transientParents = transient_.getParents();
  1370.       for (var j = 0; j < transientParents.length; j++) {
  1371.         transientParents[j].children.remove(transient_);
  1372.       }
  1373.       if (transient_.children) {
  1374.         var childEnum = transient_.children.enumerate();
  1375.         while (childEnum.hasMoreElements()) {
  1376.           var child = childEnum.getNext();
  1377.           transient_.children.remove(child);
  1378.           if (!child.getParent()) child.destroy(); 
  1379.         }
  1380.       }
  1381.       transient_.destroy();
  1382.     }
  1383.   }
  1384.  
  1385.   // Delete transient accounts
  1386.   var transientAccounts = this.faves_coop.Account.find({isTransient: true});
  1387.   for (var j = 0; j < transientAccounts.length; j++) {
  1388.     var acct = transientAccounts[j];
  1389.     this._logger.info("Removing transient Account named " + acct.id());
  1390.     Cc[acct.serviceId].getService(Ci.flockIWebService)
  1391.                       .removeAccount(acct.id());
  1392.   }
  1393. }  
  1394.  
  1395. favoritesService.prototype.Seq = function (node) {
  1396.   // make the resource a sequence if its not and return a container object
  1397.   if (!this.isSeq(node)) {
  1398.     return RDFCU.MakeSeq(this.dataSource, node);
  1399.   }
  1400.   var container = Cc[CONTAINER_CONTRACTID].createInstance(Ci.nsIRDFContainer);
  1401.   container.Init(this.dataSource, node);
  1402.   return container;
  1403. }
  1404.  
  1405. favoritesService.prototype._Seq = favoritesService.prototype.Seq;
  1406.  
  1407. favoritesService.prototype.isSeq = function (node) {
  1408.   // this._logger.info ("Called isSeq with node " + node + "\n");
  1409.   // is the node a sequence
  1410.   return RDFCU.IsSeq(this.dataSource, node);
  1411. }
  1412.  
  1413.  
  1414. favoritesService.prototype.getContainedActions = function (aURL)
  1415. {
  1416.   return simpleEnumerator(this._getContainedActions(aURL, null));
  1417. }
  1418.  
  1419. favoritesService.prototype._getContainedActions =
  1420. function (aURL, aArrIdentityActions)
  1421. {
  1422.   var getIdentityActions = function (aIdentity, identityActions) {
  1423.     var enabledActions = aIdentity.enabledAction.enumerate();
  1424.     while (enabledActions.hasMoreElements()) {
  1425.       var action = enabledActions.getNext();
  1426.       this._logger.info("adding action " + action.name + " to identityActions ");
  1427.       identityActions.push({action: action, actionName: action.name, identity: aIdentity});
  1428.     }
  1429.   };
  1430.   if (!aArrIdentityActions) {
  1431.     aArrIdentityActions = [];
  1432.   }
  1433.   var container = this.faves_coop.get(aURL);
  1434.   if (container instanceof this.faves_coop.Folder) {
  1435.     // Recurse for each contained Person
  1436.     var persons = container.children.enumerate();
  1437.     while (persons.hasMoreElements()) {
  1438.       this._getContainedActions(aChild.id(), aArrIdentityActions);
  1439.     }
  1440.   }
  1441.   if (container instanceof this.faves_coop.Person) {
  1442.     var identities = container.children.enumerate();
  1443.     while (identities.hasMoreElements()) {
  1444.       var identity = identities.getNext();
  1445.       getIdentityActions(identity, aArrIdentityActions);
  1446.     }
  1447.   }
  1448.   // return an enumerator of actions?
  1449.   return aArrIdentityActions;
  1450. }
  1451.  
  1452. favoritesService.prototype.createSeparatorInContainer =
  1453. function (aParentFolder, aIndex)
  1454. {
  1455.   var parentFolder = this.faves_coop.get_from_resource(aParentFolder);
  1456.   if (parentFolder instanceof this.faves_coop.Stream) throw NS_RDF_ASSERTION_REJECTED;
  1457.   var newSeparator = new this.faves_coop.Separator();
  1458.   parentFolder.children.insertAt(newSeparator, aIndex);
  1459.   return RDFS.GetResource(newSeparator.id());
  1460. }
  1461.  
  1462.  
  1463. favoritesService.prototype.createNewBookmarkWithTags =
  1464. function (aName, aURL, aShortcutURL, aDescription, aDocCharSet, aPostData, aTags)
  1465. {
  1466.   var now = new Date();
  1467.   
  1468.   if (!aURL || aURL == null) aURL = "about:blank";
  1469.  
  1470.   var shared = false;
  1471.   if ( PREFS.getPrefType("flock.bookmarks.shouldnotshare") &&
  1472.        PREFS.getBoolPref("flock.bookmarks.shouldnotshare") == false )
  1473.   {
  1474.     shared = true;
  1475.   }
  1476.   return new this.faves_coop.Bookmark(
  1477.       {
  1478.       name: aName,
  1479.       URL: aURL,
  1480.       description: aDescription,
  1481.       tags: aTags,
  1482.       shared: shared,
  1483.       ShortcutURL: aShortcutURL,
  1484.       BookmarkAddDate: now,
  1485.       LastModifiedDate: now,
  1486.       LastVisitDate: now,
  1487.       LastCharset: aDocCharSet
  1488.     }
  1489.   );
  1490. }
  1491.  
  1492. favoritesService.prototype.setBookmarksToolbarFolder = function (aSource)
  1493. {
  1494.   var folder = this.faves_coop.get_from_resource(aSource);
  1495.   if (folder) {
  1496.     this.faves_coop.toolbar.folder = folder;
  1497.   }
  1498. }
  1499.  
  1500. favoritesService.prototype.removeFolder = function (aFolder)
  1501. {
  1502.   // verify the folderness
  1503.   if (!this.isFolderResource(aFolder)) {
  1504.     return false;
  1505.   }
  1506.   return true;
  1507.   // FIXME: remove all the children
  1508.   // FIXME: gc the children as they're removed
  1509.   // FIXME: remove the name and the type
  1510.   // FIXME: remove the Seq-ness
  1511.   // FIXME: remove the folder from it's parent
  1512.   // FIXME: return success
  1513. }
  1514.  
  1515. favoritesService.prototype.renameFolder =
  1516. function (aFolder, aNewName)
  1517. {
  1518.   // verify the folderness
  1519.   if (!this.isFolderResource(aFolder)) {
  1520.     return false;
  1521.   }
  1522.   return true;
  1523.   // FIXME: get the old name...
  1524.   // FIXME: ds.Change RDF_NAME
  1525.   // FIXME: return success
  1526. }
  1527.  
  1528. favoritesService.prototype.moveFolder =
  1529. function (aFolder, aNewParent)
  1530. {
  1531.   // verify the folderness
  1532.   if (!this.isFolderResource(aFolder)) {
  1533.       return false;
  1534.   }
  1535.   var folder = this.faves_coop.get(aFolder.ValueUTF8);
  1536.   var newparent = this.faves_coop.favorites_root;
  1537.   if (aNewParent != null) {
  1538.     newparent = this.faves_coop.get(aNewParent.ValueUTF8);
  1539.   }
  1540.   return true;
  1541.   // FIXME: get old parent folder
  1542.   // FIXME: remove from old parent folder
  1543.   // FIXME: add to new parent folder
  1544.   // FIXME: return success
  1545. }
  1546.  
  1547. favoritesService.prototype.getFolderName = function (aFolder)
  1548. {
  1549.   // verify the folderness
  1550.   if (!this.isFolderResource(aFolder)) {
  1551.     return null;
  1552.   }
  1553.   var folder = this.faves_coop.get(aFolder.ValueUTF8);
  1554.   return folder.name;
  1555. }
  1556.  
  1557. favoritesService.prototype.createBookmarkInContainer =
  1558. function (aName, aURL, aShortcutURL, aDescription, aDocCharSet, aPostData, aFolder, aIndex)
  1559. {
  1560.   var bookmarkResource = this.createBookmark(aName, aURL, aShortcutURL, aDescription, aDocCharSet, aPostData);
  1561.   var bookmark = this.faves_coop.get(bookmarkResource.Value);
  1562.   var folder = this.faves_coop.get(aFolder.ValueUTF8);
  1563.  
  1564.   // FIXME: use index?
  1565.   folder.children.add(bookmark);
  1566.  
  1567.   //??? this.removeFavoriteFromFolder(aURL, this.favoritesRoot);
  1568. }
  1569.  
  1570. favoritesService.prototype.__defineGetter__("migrationName",
  1571. function favoritesService_getter_migrationName() {
  1572.   return "Bookmarks";
  1573. });
  1574.  
  1575. favoritesService.prototype._migrateFavorites = function (ctxt)
  1576. {
  1577.   // a coop version of the bookmarks_root folder
  1578.   var realBookmarksRootFolder = this.faves_coop.bookmarks_root.children;
  1579.   // read into in-memory rdf
  1580.   var ds = RDFS.GetDataSourceBlocking(IOS.newFileURI(ctxt.oldFavoritesFile).spec);
  1581.   // coopify the rdf graph
  1582.   var resourceCount = 0;
  1583.   var resources = ds.GetAllResources();
  1584.  
  1585.   // helper function
  1586.   function _addBookmarkToFolderHelper(aScope, aFolder, aFav) {
  1587.     aScope.addBookmarkToFolder(aFolder, aFav.Name, aFav.URL, null,
  1588.                                aFav.description, null, null, aFav.tags);
  1589.   }
  1590.  
  1591.   while (resources.hasMoreElements()) {
  1592.     var resource = resources.getNext();
  1593.     var type_target = ds.GetTarget(resource, RDF_TYPE, true);
  1594.     if (type_target) {
  1595.       ds.Assert(resource, RDF_COOPTYPE, type_target, true);
  1596.       resourceCount++;
  1597.     }
  1598.   }
  1599.   var Coop = loadSubScript("chrome://flock/content/common/coop.js").Coop;
  1600.   var migration_coop = new Coop(ds, "chrome://flock/content/common/migration-coop.js");
  1601.   var folderRes;
  1602.   var favEnum = migration_coop.CardinalFavorite.all();
  1603.   var favesCount = 0;
  1604.   var folderID = this.faves_coop.bookmarks_root.id();
  1605.   while (favEnum.hasMoreElements()) {
  1606.     var fave = favEnum.getNext();
  1607.     if (!fave.Name) continue;
  1608.     if (fave.URL == "http://flock.com/rdf#FavoritesRoot") continue;
  1609.     var favParents = fave.getParents();
  1610.     if (favParents) {
  1611.       // fave.getParents() possibly has an array of null parents. These are
  1612.       // the bookmarks that are not put into a collection or bookmarks toolbar
  1613.       // in Cardinal. If there is only 1 entry in array, we need to add
  1614.       // that bookmark to the bookmarks root.
  1615.       if (favParents.length == 1 && !favParents[0]) {
  1616.         _addBookmarkToFolderHelper(this, folderID, fave);
  1617.       }
  1618.     }
  1619.     favesCount++;
  1620.     var percentage = Math.round(100 * (favesCount / resourceCount));
  1621.     ctxt.listener.onUpdate(percentage, "Migrating favorites");
  1622.     yield false;
  1623.   }
  1624.  
  1625.   // migrate collections
  1626.   var collEnum = migration_coop.CardinalCollection.all();
  1627.   var folderRes = RDFS.GetResource(this.faves_coop.bookmarks_root.id());
  1628.   while (collEnum.hasMoreElements()) {
  1629.     var coll = collEnum.getNext();
  1630.     var folderIndex = -1;
  1631.     if (!coll) {
  1632.       // We have to do this since anything that doesn't match the
  1633.       // migrations-coop schema is returned as null.
  1634.       continue;
  1635.     }
  1636.     if (!coll.Name) {
  1637.       continue;
  1638.     }
  1639.     if (coll.toolbar) {
  1640.       folderIndex = 1;
  1641.     }
  1642.     var folder = this.createFolderInContainer(coll.Name, folderRes, folderIndex);
  1643.     var collContents = coll.children.enumerate();
  1644.     while (collContents.hasMoreElements()) {
  1645.       fave = collContents.getNext();
  1646.       if (!fave) {
  1647.         // We have to do this since anything that doesn't match the
  1648.         // migrations-coop schema is returned as null.
  1649.         continue;
  1650.       }
  1651.       // Add bookmark into new folder from collection in Cardinal.
  1652.       _addBookmarkToFolderHelper(this, folder.ValueUTF8, fave);
  1653.     }
  1654.     if (coll.toolbar) {
  1655.       // Get the new folder's coop representation.
  1656.       var newFolder = this.faves_coop.get(folder.ValueUTF8);
  1657.       
  1658.       // get the bm toolbar folder name from Cormorant
  1659.       var bmToolbarId = this.getBookmarksToolbarFolder().Value;
  1660.       var bmToolbarName = this.faves_coop.get(bmToolbarId).name;
  1661.  
  1662.       // delete the default bookmark toolbar folder
  1663.       this.faves_coop.get(bmToolbarId).destroy();
  1664.  
  1665.       this.faves_coop.toolbar.folder = newFolder;
  1666.       
  1667.       // Change the toolbar folder name to Cormorant's folder name.
  1668.       this.faves_coop.toolbar.folder.name = bmToolbarName;
  1669.     }
  1670.   }
  1671.  
  1672.   // rename the file and delete the old copy
  1673.   RDFS.UnregisterDataSource(ds);
  1674.   ds = null;
  1675.   migration_coop.datasource = null;
  1676.   migration_coop = null;
  1677.   
  1678.   ctxt.oldFavoritesFile.moveTo(null, OLD_FAVORITES_RDF_FILE_RELIC);
  1679.   yield true;
  1680. }
  1681.  
  1682.  
  1683. favoritesService.prototype.notifyResolvedCharset =
  1684. function FS_notifyResolvedCharset (aCharset, aClosure) {
  1685.   throw Components.Exception("Unexpected call to NotifyResolvedCharset -- we never set aWantCharset to true!",
  1686.                              Cr.NS_ERROR_UNEXPECTED);
  1687. }
  1688.  
  1689. favoritesService.prototype.requestCharset =
  1690. function FS_requestCharset (aWebNavigation, aChannel, aWantCharset, aClosure) {
  1691.   aWantCharset.value = false;
  1692.   aClosure.value = null;
  1693.   
  1694.   var url = aChannel.URI.spec;
  1695.  
  1696.   var fav = this.faves_coop.get(url);
  1697.   if (fav && fav.LastCharset) {
  1698.     return fav.LastCharset;
  1699.   } else {
  1700.     return "";
  1701.   } 
  1702. }
  1703.  
  1704.  
  1705. // ================================================
  1706. // ========== BEGIN XPCOM Module support ==========
  1707. // ================================================
  1708.  
  1709. function createModule(aParams) {
  1710.   return {
  1711.     registerSelf: function (aCompMgr, aFileSpec, aLocation, aType) {
  1712.       var aCompMgr = aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1713.       aCompMgr.registerFactoryLocation( aParams.CID, aParams.componentName,
  1714.                                         aParams.contractID, aFileSpec,
  1715.                                         aLocation, aType );
  1716.       aCompMgr.registerFactoryLocation( aParams.CID, aParams.componentName,
  1717.                                         "@mozilla.org/embeddor.implemented/bookmark-charset-resolver;1", aFileSpec,
  1718.                                         aLocation, aType );
  1719.       var catMgr = Cc["@mozilla.org/categorymanager;1"]
  1720.         .getService(Ci.nsICategoryManager);
  1721.       if (!aParams.categories) { aParams.categories = []; }
  1722.       for (var i = 0; i < aParams.categories.length; i++) {
  1723.         var cat = aParams.categories[i];
  1724.         catMgr.addCategoryEntry( cat.category, cat.entry,
  1725.                                  cat.value, true, true );
  1726.       }
  1727.     },
  1728.     getClassObject: function (aCompMgr, aCID, aIID) {
  1729.       if (!aCID.equals(aParams.CID)) { throw Cr.NS_ERROR_NO_INTERFACE; }
  1730.       if (!aIID.equals(Ci.nsIFactory)) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; }
  1731.       return { // Factory
  1732.         createInstance: function (aOuter, aIID) {
  1733.           if (aOuter != null) { throw Cr.NS_ERROR_NO_AGGREGATION; }
  1734.           var comp = new aParams.componentClass();
  1735.           if (aParams.implementationFunc) { aParams.implementationFunc(comp); }
  1736.           return comp.QueryInterface(aIID);
  1737.         }
  1738.       };
  1739.     },
  1740.     canUnload: function (aCompMgr) { return true; }
  1741.   };
  1742. }
  1743.  
  1744. // NS Module entrypoint
  1745. function NSGetModule(aCompMgr, aFileSpec) {
  1746.   return createModule({
  1747.     componentClass: favoritesService,
  1748.     CID: FAVORITES_CID,
  1749.     contractID: FAVORITES_CONTRACTID,
  1750.     componentName: "Favorites JS Component",
  1751.     categories: [
  1752.       { category: "flockMigratable", entry: "favorites", value: FAVORITES_CONTRACTID }
  1753.     ]
  1754.   });
  1755.  
  1756. }
  1757.  
  1758. // ========== END XPCOM Module support ==========
  1759.